#Region "Using Directives"

Imports System
Imports System.Collections
Imports System.Text

#End Region

Namespace C4F.DevKit.Bluetooth.ObjectExchange

    ''' <summary>
    ''' Represents an Object Exchange packet.
    ''' </summary>
    Public Class ObjectExchangeItem

#Region "Private Fields"

        ''' <summary>
        ''' List of headers contained in the packet.
        ''' </summary>
        Private headers As Hashtable

        ''' <summary>
        ''' Opcode (Request Code/Response Code) of the packet.
        ''' </summary>
        Private m_opcode As ObjectExchangeOpcode

        ''' <summary>
        ''' Specifies whether the Security Model is in use.
        ''' </summary>
        Private securityModelUsed As Boolean

        ''' <summary>
        ''' Security Model to be used.
        ''' </summary>
        Private m_objectExchangeSecurityModel As ObjectExchangeSecurity

#End Region

#Region "Private Constants"

        ''' <summary>
        ''' Opcode for Nonce value in AuthenticationChallenge header.
        ''' </summary>
        Private Const AUTHCHALLENGE_NONCE_OPCODE As Byte = 0

        ''' <summary>
        ''' Opcode for Options value in AuthenticationChallenge header.
        ''' </summary>
        Private Const AUTHCHALLENGE_OPTIONS_OPCODE As Byte = 1

        ''' <summary>
        ''' Opcode for Realm value in AuthenticationChallenge header.
        ''' </summary>
        Private Const AUTHCHALLENGE_REALM_OPCODE As Byte = 2

        ''' <summary>
        ''' Opcode for Request Digest value in AuthenticationResponse header.
        ''' </summary>
        Private Const AUTHRESPONSE_REQUESTDIGEST_OPCODE As Byte = 0

        ''' <summary>
        ''' Opcode for UserId value in AuthenticationResponse header.
        ''' </summary>
        Private Const AUTHRESPONSE_USERID_OPCODE As Byte = 1

        ''' <summary>
        ''' Opcode for Nonce value in AuthenticationResponse header.
        ''' </summary>
        Private Const AUTHRESPONSE_NONCE_OPCODE As Byte = 2

        ''' <summary>
        ''' Length of Nonce value.
        ''' </summary>
        Private Const NONCE_LENGTH As Integer = 16

        ''' <summary>
        ''' Length of Options value in AuthenticationChallenge header.
        ''' </summary>
        Private Const AUTHCHALLENGE_OPTIONS_LENGTH As Integer = 1

        ''' <summary>
        ''' Length of Request Digest value in AuthenticationResponse header.
        ''' </summary>
        Private Const REQUESTDIGEST_LENGTH As Integer = 16

#End Region

#Region "Public Properties"

        ''' <summary>
        ''' Gets or sets the Security Model object.
        ''' </summary>
        Public Property ObjectExchangeSecurityModel() As ObjectExchangeSecurity

            Get
                Return m_objectExchangeSecurityModel
            End Get

            Set(ByVal value As ObjectExchangeSecurity)

                m_objectExchangeSecurityModel = value
                If value Is Nothing Then
                    securityModelUsed = False
                Else
                    securityModelUsed = True
                End If

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the Opcode (Request Code/Response Code) of the 
        ''' ObjectExchange Packet.
        ''' </summary>
        ''' <exception cref="ArgumentException">The opcode to be assigned is 
        ''' not of type ObjectExchangeOpcode.</exception>
        Public Property Opcode() As ObjectExchangeOpcode

            Get
                Return m_opcode
            End Get

            Set(ByVal value As ObjectExchangeOpcode)
                If Not System.Enum.IsDefined(GetType(ObjectExchangeOpcode), value) Then
                    Throw New ArgumentException(ObjectExchangeResources.Error_InvalidOpcode)
                End If

                m_opcode = value
            End Set

        End Property

        ''' <summary>
        ''' Gets the size in bytes of the ObjectExchange Packet.
        ''' </summary>
        Public ReadOnly Property PacketSize() As Integer

            Get
                Return GetByteStream().Length
            End Get

        End Property

#End Region

#Region "Constructors"

        ''' <summary>
        ''' Creates a new instance of ObjectExchangeItem with Opcode property
        ''' set as the specified opcode and an empty list of headers.
        ''' </summary>
        ''' <param name="opcode">Opcode of the Object Exchange packet (Request 
        ''' Code/Response Code)</param>
        ''' <exception cref="ArgumentException">Opcode is not of type 
        ''' ObjectExchangeOpcode.</exception>
        Public Sub New(ByVal opcode As ObjectExchangeOpcode)

            ' Initialize the header list.
            headers = New Hashtable()
            ' Initialize the Opcode property with the specified opcode.
            Me.Opcode = opcode

        End Sub

        ''' <summary>
        ''' Create a new instance of ObjectExchangeItem from the specified
        ''' memory stream. The memory stream is parsed for the opcode of the 
        ''' Object Exchange packet and headers which are added to an internally
        ''' maintained list of headers.
        ''' </summary>
        ''' <param name="objectExchangeData">Memory stream that will be parsed
        ''' for ObjectExchangeItem fields and headers.</param>
        ''' <exception cref="ArgumentNullException">ObjectExchangeData is passed
        ''' as a null reference.</exception>
        ''' <exception cref="ArgumentException">ObjectExchangeData has length
        ''' less than 3 bytes. The minimum size of an Object Exchange packet is
        ''' 3 bytes.</exception>
        ''' <exception cref="ObjectExchangeException">ObjectExchangeData could 
        ''' not be parsed.</exception>
        Public Sub New(ByVal objectExchangeData As IO.MemoryStream)

            If objectExchangeData Is Nothing Then
                Throw New ArgumentNullException("objectExchangeData")
            End If

            ' Initialize the header list.
            headers = New Hashtable()
            Parse(objectExchangeData)

        End Sub

        ''' <summary>
        ''' Create a new instance of ObjectExchangeItem from the specified
        ''' byte stream. The memory stream is parsed for the opcode of the 
        ''' Object Exchange packet and headers which are added to an internally
        ''' maintained list of headers.
        ''' </summary>
        ''' <param name="objectExchangeData">Byte stream that will be parsed
        ''' for ObjectExchangeItem fields and headers.</param>
        ''' <exception cref="ArgumentNullException">ObjectExchangeData is passed
        ''' as a null reference.</exception>
        ''' <exception cref="ArgumentException">ObjectExchangeData has length
        ''' less than 3 bytes. The minimum size of an Object Exchange packet is
        ''' 3 bytes.</exception>
        ''' <exception cref="ObjectExchangeException">ObjectExchangeData could 
        ''' not be parsed.</exception>
        Public Sub New(ByVal objectExchangeData() As Byte)

            If objectExchangeData Is Nothing Then
                Throw New ArgumentNullException("objectExchangeData")
            End If

            ' Initialize the header list.
            headers = New Hashtable()
            Dim memStream As New IO.MemoryStream(objectExchangeData)
            Try
                Parse(memStream)
            Finally
                memStream.Close()
            End Try

        End Sub

#End Region

#Region "Public Functions"

        ''' <summary>
        ''' Adds an AuthenticationChallenge Header to the internally maintained
        ''' list of headers of the current instance of ObjectExchangeItem 
        ''' provided the Security Model is in use.
        ''' </summary>
        ''' <exception cref="InvalidOperationException">The Security Model has 
        ''' not been set.</exception>
        ''' <exception cref="IO.InvalidDataException">An invalid Nonce value has
        ''' been set in the Security Model.</exception>
        ''' <exception cref="OverflowException">The realm value set in the
        ''' Security Model is too large.</exception>
        Public Sub AddAuthenticationChallengeHeader()

            If Not securityModelUsed Then
                Throw New InvalidOperationException(ObjectExchangeResources.Error_SecurityModelNotSet)
            End If

            Dim headerValueStream As New IO.MemoryStream()

            Try
                '  "Nonce".

                Dim nonce() As Byte = m_objectExchangeSecurityModel.ChallengeData.Nonce
                If nonce Is Nothing OrElse nonce.Length <> NONCE_LENGTH Then
                    Throw New IO.InvalidDataException(ObjectExchangeResources.Error_InvalidNonce)
                End If

                'Write tag-length-value for Nonce.
                With headerValueStream
                    .WriteByte(AUTHCHALLENGE_NONCE_OPCODE)
                    .WriteByte(NONCE_LENGTH)
                    .Write(nonce, 0, NONCE_LENGTH)

                    '  "Options".

                    ' If either or both options has been set, include the options
                    ' in the header else skip.
                    If Not (Not (m_objectExchangeSecurityModel.ChallengeData.RequestUserId) AndAlso Not (m_objectExchangeSecurityModel.ChallengeData.ReadOnlyAccess)) Then
                        'Write tag-length-value for Options.
                        .WriteByte(AUTHCHALLENGE_OPTIONS_OPCODE)
                        .WriteByte(AUTHCHALLENGE_OPTIONS_LENGTH)

                        ' Note that Options byte is as follows:
                        ' 000000ra where 
                        ' bit 'r' indicates whether userId is to be requested
                        ' bit 'a' indicates the access mode (ReadOnly or not).
                        If m_objectExchangeSecurityModel.ChallengeData.RequestUserId AndAlso Not (m_objectExchangeSecurityModel.ChallengeData.ReadOnlyAccess) Then
                            .WriteByte(1)
                        ElseIf Not (m_objectExchangeSecurityModel.ChallengeData.RequestUserId) AndAlso m_objectExchangeSecurityModel.ChallengeData.ReadOnlyAccess Then
                            .WriteByte(2)
                        Else
                            .WriteByte(3)
                        End If
                    End If
                End With


                ' "Realm".
                With headerValueStream

                    Dim realm As String = m_objectExchangeSecurityModel.ChallengeData.Realm
                    ' If realm has been specified, include the realm in the header
                    ' else skip.
                    If Not String.IsNullOrEmpty(realm) Then
                        Dim realmLength As Integer = Encoding.[Default].GetByteCount(realm)
                        If realmLength > 255 Then
                            Throw New OverflowException(ObjectExchangeResources.Error_InvalidRealm)
                        End If
                        Dim realmBytes() As Byte = Encoding.[Default].GetBytes(realm)

                        ' Write tag-length-value for Realm.
                        .WriteByte(AUTHCHALLENGE_REALM_OPCODE)
                        .WriteByte(Convert.ToByte(realmLength))
                        .Write(realmBytes, 0, realmLength)
                    End If

                    ' Add the AuthenticateChallenge header to the header list.
                    headers.Add(ObjectExchangeHeaderType.AuthenticationChallenge, .ToArray())
                End With
            Finally
                headerValueStream.Dispose()
            End Try

        End Sub

        ''' <summary>
        ''' Adds the AuthenticationResponse Header to the internally maintained
        ''' list of headers of the current instance of ObjectExchangeItem 
        ''' provided the Security Model is in use.
        ''' </summary>
        ''' <exception cref="InvalidOperationException">The Security Model has 
        ''' not been set.</exception>
        ''' <exception cref="IO.InvalidDataException">The Request digest value 
        ''' generated by the Security Model has an invalid value OR An invalid Nonce value has
        ''' been set in the Security Model.</exception>
        ''' <exception cref="OverflowException">The UserId value set in the 
        ''' Security Model is too large.</exception>
        Public Sub AddAuthenticationResponseHeader()
            If Not securityModelUsed Then
                Throw New InvalidOperationException(ObjectExchangeResources.Error_SecurityModelNotSet)
            End If

            Dim headerValueStream As New IO.MemoryStream()

            Try
                ' "Request Digest".

                Dim requestDigest() As Byte = m_objectExchangeSecurityModel.ResponseData.GenerateRequestDigest()
                If requestDigest Is Nothing OrElse requestDigest.Length <> REQUESTDIGEST_LENGTH Then
                    Throw New IO.InvalidDataException(ObjectExchangeResources.Error_InvalidRequestDigest)
                End If

                With headerValueStream
                    ' Write tag-length-value for Request Digest.
                    .WriteByte(AUTHRESPONSE_REQUESTDIGEST_OPCODE)
                    .WriteByte(REQUESTDIGEST_LENGTH)
                    .Write(requestDigest, 0, REQUESTDIGEST_LENGTH)

                    '  "UserID".

                    ' If userId has been set in the Security Model, include it in
                    ' the header else skip.
                    Dim userId As String = m_objectExchangeSecurityModel.ResponseData.UserId
                    If Not String.IsNullOrEmpty(userId) Then
                        Dim userIdLength As Integer = Encoding.[Default].GetByteCount(userId)
                        If userIdLength > 255 Then
                            Throw New OverflowException(ObjectExchangeResources.Error_InvalidUserId)
                        End If
                        Dim userIdBytes() As Byte = Encoding.[Default].GetBytes(userId)

                        ' Write tag-length-value for UserId.
                        .WriteByte(AUTHRESPONSE_USERID_OPCODE)
                        .WriteByte(Convert.ToByte(userIdLength))
                        .Write(userIdBytes, 0, userIdLength)
                    End If

                    ' "Nonce"

                    Dim nonce() As Byte = m_objectExchangeSecurityModel.ResponseData.Nonce
                    If nonce Is Nothing OrElse nonce.Length <> NONCE_LENGTH Then
                        Throw New IO.InvalidDataException(ObjectExchangeResources.Error_InvalidNonce)
                    End If

                    ' Write tag-length-value for Nonce.
                    .WriteByte(AUTHRESPONSE_NONCE_OPCODE)
                    .WriteByte(NONCE_LENGTH)
                    .Write(nonce, 0, NONCE_LENGTH)

                    ' Add the AuthenticateResponse header to the header list.
                    headers.Add(ObjectExchangeHeaderType.AuthenticationResponse, .ToArray())
                End With

            Finally
                headerValueStream.Close()
            End Try
        End Sub

        ''' <summary>
        ''' Adds a Header to the list of headers internally maintained in the 
        ''' current instance of ObjectExchangeItem.
        ''' </summary>
        ''' <param name="headerType">The type of Header to add.</param>
        ''' <param name="headerValue">The Header value in the form of a byte
        ''' stream.</param>
        ''' <exception cref="ArgumentException">HeaderType is not of type
        ''' ObjectExchangeHeaderType or headerType already exists in the
        ''' internally maintained list of headers or headerValue is an invalid
        ''' value.
        ''' </exception>
        ''' <exception cref="ArgumentNullException">HeaderValue is passed as a
        ''' null reference.</exception>
        ''' <exception cref="InvalidOperationException">The addition of the 
        ''' headerType creates a conflict with a header that exists in the
        ''' internally maintained list of headers. For example, an Object 
        ''' Exchange packet should not contain both Body and EndOfBody headers.
        ''' </exception>
        Public Sub AddHeader(ByVal headerType As ObjectExchangeHeaderType, ByVal headerValue() As Byte)
            If Not System.Enum.IsDefined(GetType(ObjectExchangeHeaderType), headerType) Then
                Throw New ArgumentException(ObjectExchangeResources.Error_InvalidHeaderType, "headerType")
            End If

            If headerValue Is Nothing Then
                Throw New ArgumentNullException("headerValue")
            End If

            If headers.ContainsKey(headerType) Then
                Throw New ArgumentException(ObjectExchangeResources.Error_HeaderTypeExists)
            End If

            ' "Check for conflicting header types".

            ' Body and EndOfBody headers should not co-exist in the same
            ' packet.
            If (headerType = ObjectExchangeHeaderType.Body AndAlso headers.Contains(ObjectExchangeHeaderType.EndOfBody)) OrElse (headerType = ObjectExchangeHeaderType.EndOfBody AndAlso headers.Contains(ObjectExchangeHeaderType.Body)) Then
                Throw New InvalidOperationException(ObjectExchangeResources.Error_BodyEndOfBodyConflict)
            End If

            ' It is illegal to send a Connection Id and a Target header in the
            ' same request. But It is legal to send both headers in a response.
            If System.Enum.IsDefined(GetType(ObjectExchangeRequestCode), Me.m_opcode.ToString()) AndAlso ((headerType = ObjectExchangeHeaderType.ConnectionID AndAlso headers.Contains(ObjectExchangeHeaderType.Target)) OrElse (headerType = ObjectExchangeHeaderType.Target AndAlso headers.Contains(ObjectExchangeHeaderType.ConnectionID))) Then
                Throw New InvalidOperationException(ObjectExchangeResources.Error_ConnIdTargetConflict)
            End If

            ' "Header value validations"

            ' Since Name and EndOfBody headers can be empty.
            If headerType <> ObjectExchangeHeaderType.Name AndAlso headerType <> ObjectExchangeHeaderType.EndOfBody Then
                If headerValue.Length = 0 Then
                    Throw New ArgumentException(ObjectExchangeResources.Error_EmptyHeaderValue, "headerValue")
                End If

                If (headerType = ObjectExchangeHeaderType.ObexVersionNumber OrElse headerType = ObjectExchangeHeaderType.Flags OrElse headerType = ObjectExchangeHeaderType.Constants) AndAlso headerValue.Length <> 1 Then
                    Throw New ArgumentException([String].Format(ObjectExchangeResources.Error_HeaderValueLength, headerType.ToString(), 1), "headerValue")
                    ' 1 byte quantity.
                End If

                If headerType = ObjectExchangeHeaderType.MaxObexPacketLength AndAlso headerValue.Length <> 2 Then
                    Throw New ArgumentException([String].Format(ObjectExchangeResources.Error_HeaderValueLength, headerType.ToString(), 2), "headerValue")
                    ' 2 byte quantity.
                End If

                If (headerType = ObjectExchangeHeaderType.Time4ByteVersion OrElse headerType = ObjectExchangeHeaderType.Length OrElse headerType = ObjectExchangeHeaderType.Count OrElse headerType = ObjectExchangeHeaderType.ConnectionID) AndAlso headerValue.Length <> 4 Then
                    Throw New ArgumentException([String].Format(ObjectExchangeResources.Error_HeaderValueLength, headerType.ToString(), 4), "headerValue")
                    '4 byte quantity.
                End If
            End If

            ' Add the header to the list of headers.
            headers.Add(headerType, headerValue)
        End Sub

        ''' <summary>
        ''' Adds a Header to the list of headers internally maintained in the 
        ''' current instance of ObjectExchangeItem.
        ''' </summary>
        ''' <param name="headerType">The type of Header to add.</param>
        ''' <param name="headerValue">The Header value in the form of a memory
        ''' stream.</param>
        ''' <exception cref="ArgumentException">HeaderType is not of type
        ''' ObjectExchangeHeaderType or headerType already exists in the
        ''' internally maintained list of headers or headerValue is an invalid
        ''' value.
        ''' </exception>
        ''' <exception cref="ArgumentNullException">HeaderValue is passed as a
        ''' null reference.</exception>
        ''' <exception cref="InvalidOperationException">The addition of the 
        ''' headerType creates a conflict with a header that exists in the
        ''' internally maintained list of headers. For example, an Object 
        ''' Exchange packet should not contain both Body and EndOfBody headers.
        ''' </exception>
        Public Sub AddHeader(ByVal headerType As ObjectExchangeHeaderType, ByVal headerValue As IO.MemoryStream)
            If headerValue Is Nothing Then
                Throw New ArgumentNullException("headerValue")
            End If

            ' Add the header to the header list.
            AddHeader(headerType, headerValue.ToArray())
        End Sub

        ''' <summary>
        ''' Adds a Header to the list of headers internally maintained in the 
        ''' current instance of ObjectExchangeItem.
        ''' Converts Name and Description header values to null-terminated Big
        ''' Endian Unicode text.
        ''' Adds null terminator to Target header value.
        ''' </summary>
        ''' <param name="headerType">The type of Header to add.</param>
        ''' <param name="headerValue">The Header value in the form of a string.
        ''' </param>
        ''' <exception cref="ArgumentException">HeaderType is not of type
        ''' ObjectExchangeHeaderType or headerType already exists in the
        ''' internally maintained list of headers or headerValue is an invalid
        ''' value.
        ''' </exception>
        ''' <exception cref="ArgumentNullException">HeaderValue is passed as a
        ''' null reference.</exception>
        ''' <exception cref="InvalidOperationException">The addition of the 
        ''' headerType creates a conflict with a header that exists in the
        ''' internally maintained list of headers. For example, an Object 
        ''' Exchange packet should not contain both Body and EndOfBody headers.
        ''' </exception>
        Public Sub AddHeader(ByVal headerType As ObjectExchangeHeaderType, ByVal headerValue As String)
            If headerValue Is Nothing Then
                Throw New ArgumentNullException("headerValue")
            End If

            ' Since Name and EndOfBody headers can be empty.
            If headerValue = String.Empty AndAlso headerType <> ObjectExchangeHeaderType.Name AndAlso headerType <> ObjectExchangeHeaderType.EndOfBody Then
                Throw New ArgumentNullException("headerValue")
            End If

            Dim headerValueBytes() As Byte
            Select Case headerType
                Case ObjectExchangeHeaderType.Name
                    ' Name should be a byte sequence of null-terminated Big
                    ' Endian Unicode text.
                    If headerValue = String.Empty Then
                        headerValueBytes = New Byte() {}
                    Else
                        headerValueBytes = Encoding.BigEndianUnicode.GetBytes(headerValue)

                    End If
                    Exit Select
                Case ObjectExchangeHeaderType.Description

                    ' Description should be a byte sequence of null-terminated 
                    ' Big Endian Unicode text.
                    headerValueBytes = Encoding.BigEndianUnicode.GetBytes(headerValue)
                    Exit Select
                Case ObjectExchangeHeaderType.Type

                    ' Type should be a byte sequence of null-terminated ASCII
                    ' text.

                    headerValueBytes = Encoding.ASCII.GetBytes(headerValue)
                    Exit Select
                Case Else

                    ' No formatting required.
                    headerValueBytes = Encoding.[Default].GetBytes(headerValue)
                    Exit Select
            End Select

            ' Add the header to the header list.
            AddHeader(headerType, headerValueBytes)
        End Sub

        ''' <summary>
        ''' Clears all headers currently maintained in the internal Header list
        ''' of the current instance of ObjectExchangeItem.
        ''' </summary>
        Public Sub RemoveAllHeaders()

            headers.Clear()

        End Sub

        ''' <summary>
        ''' Returns the value of a Header.
        ''' </summary>
        ''' <param name="headerType">The type of Header whose value is to be 
        ''' returned.</param>
        ''' <returns>A byte stream providing the value associated with the 
        ''' specified header type.</returns>
        ''' <exception cref="ArgumentException">The header type is not found
        ''' in the list of headers maintained internally by the 
        ''' ObjectExchangeItem object.</exception>
        Public Function GetHeader(ByVal headerType As ObjectExchangeHeaderType) As Byte()

            If headers.ContainsKey(headerType) Then

                Dim headerValueType() As Byte = headers(headerType)
                Return headerValueType

            Else
                Throw New ArgumentException(ObjectExchangeResources.Error_HeaderTypeDoesNotExist)
            End If

        End Function

        ''' <summary>
        ''' Returns true if the specified header type is found in the list of
        ''' headers maintained internally in the current instance of the
        ''' ObjectExchangeItem class.
        ''' </summary>
        ''' <param name="headerType">Type of Header to search for.</param>
        ''' <returns>True if the header exists else false.</returns>
        Public Function HeaderTypeExists(ByVal headerType As ObjectExchangeHeaderType) As Boolean

            If headers.ContainsKey(headerType) Then
                Return True
            Else
                Return False
            End If

        End Function

        ''' <summary>
        ''' Returns an array of header types that are present in the header 
        ''' list maintained by the ObjectExchangeItem instance.
        ''' </summary>
        ''' <returns>Array of header types that are present in the header list.
        ''' </returns>
        Public Function GetHeaderTypes() As ObjectExchangeHeaderType()

            Dim headerTypes As ObjectExchangeHeaderType() = New ObjectExchangeHeaderType(headers.Count - 1) {}
            headers.Keys.CopyTo(headerTypes, 0)
            Return headerTypes

        End Function

        ''' <summary>
        ''' Remove a Header type from the header list maintained by the 
        ''' ObjectExchangeItem instance.
        ''' </summary>
        ''' <param name="headerType">The type of Header to remove.</param>
        ''' <exception cref="ArgumentException">HeaderType is not found in the
        ''' internal header list maintained by the current instance of 
        ''' ObjectExchangeItem.</exception>
        Public Sub RemoveHeader(ByVal headerType As ObjectExchangeHeaderType)

            If headers.ContainsKey(headerType) Then
                headers.Remove(headerType)
            Else
                Throw New ArgumentException(ObjectExchangeResources.Error_HeaderTypeDoesNotExist)
            End If

        End Sub

        ''' <summary>
        ''' Streams the current ObjectExchangeItem instance into a byte stream
        ''' that can be sent to the remote device.
        ''' </summary>
        ''' <returns>Byte stream of the ObjectExchangeItem instance.</returns>
        Public Function GetByteStream() As Byte()

            Dim streamSize As UShort = 0
            Dim memoryStream As New IO.MemoryStream()
            Dim streamWriter As New IO.BinaryWriter(memoryStream)

            Try
                ' Write the opcode (Request code/Response code) in the 1st 
                ' byte.

                streamWriter.Write(Convert.ToByte(m_opcode))

                ' Leave 2 bytes for the packet length.
                streamWriter.Seek(2, IO.SeekOrigin.Current)
                streamSize = 3

                ' "Write ObjectExchange packet Fields"

                ' Write the fields of the ObjectExchange packet if any.
                If headers.ContainsKey(ObjectExchangeHeaderType.ObexVersionNumber) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.ObexVersionNumber, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Flags) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Flags, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Constants) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Constants, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.MaxObexPacketLength) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.MaxObexPacketLength, streamWriter)
                End If

                ' "Write ObjectExchange packet Headers".

                ' Write the headers of the ObjectExchange packet if any.
                ' Check whether a ConnectionId header is to be added first
                ' since if used, it should be the first header of the packet.
                If headers.ContainsKey(ObjectExchangeHeaderType.ConnectionID) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.ConnectionID, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Who) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Who, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Target) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Target, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.AuthenticationChallenge) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.AuthenticationChallenge, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.AuthenticationResponse) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.AuthenticationResponse, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Type) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Type, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Name) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Name, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Length) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Length, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Count) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Count, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Body) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Body, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.EndOfBody) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.EndOfBody, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Description) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Description, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.TimeISO) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.TimeISO, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.Time4ByteVersion) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.Time4ByteVersion, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.HTTP) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.HTTP, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.ApplicationParameters) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.ApplicationParameters, streamWriter)
                End If

                If headers.ContainsKey(ObjectExchangeHeaderType.ObjectClass) Then
                    streamSize += WriteHeaderToStream(ObjectExchangeHeaderType.ObjectClass, streamWriter)
                End If

                'Write the length of the stream in the 2nd byte.
                streamWriter.Seek(1, IO.SeekOrigin.Begin)
                streamWriter.Write(ObjectExchangeHelper.FormatUInt16(streamSize))

                Return memoryStream.ToArray()
            Finally
                streamWriter.Close()
            End Try

        End Function

#End Region

#Region "Private Functions"

        ''' <summary>
        ''' Given a Memory Stream, parses it assuming that it is an 
        ''' ObjectExchangeItem object.
        ''' </summary>
        ''' <param name="objectExchangeData">Memory stream to parse.</param>
        ''' <exception cref="ArgumentNullException">ObjectExchangeData is 
        ''' passed as a null reference.</exception>
        ''' <exception cref="ArgumentException">ObjectExchangeData has length
        ''' less than 3 bytes. The minimum size of an Object Exchange packet is
        ''' 3 bytes.</exception>
        ''' <exception cref="ObjectExchangeException">The stream could not be
        ''' parsed.</exception>
        Private Sub Parse(ByVal objectExchangeData As IO.MemoryStream)

            If objectExchangeData Is Nothing Then
                Throw New ArgumentNullException("objectExchangeData")
            End If

            If objectExchangeData.Length < 3 Then
                Throw New ArgumentException(ObjectExchangeResources.Error_MinObexPacketSize, "objectExchangeData")
            End If

            Dim streamReader As New IO.BinaryReader(objectExchangeData)

            ' Get the opcode (Request Code/Response Code) of the packet.
            Opcode = CType(streamReader.ReadByte(), ObjectExchangeOpcode)

            ' Get the length of the packet (2 bytes).
            Dim packetSize As UShort = ObjectExchangeHelper.FormatUInt16(streamReader.ReadUInt16())
            If packetSize > objectExchangeData.Length Then
                Throw New ObjectExchangeException(ObjectExchangeErrorType.StreamError, ObjectExchangeResources.Error_InvalidObexPacket)
            End If

            With headers
                ' Check if it was a Connect request.
                If Opcode = ObjectExchangeOpcode.Connect Then
                    ' Read the Obex version number, Flags and maximum obex packet
                    ' length fields.
                    .Add(ObjectExchangeHeaderType.ObexVersionNumber, New Byte() {streamReader.ReadByte()})
                    .Add(ObjectExchangeHeaderType.Flags, New Byte() {streamReader.ReadByte()})
                    .Add(ObjectExchangeHeaderType.MaxObexPacketLength, streamReader.ReadBytes(2))
                End If

                ' Check if it was a SetPath request.
                If Opcode = ObjectExchangeOpcode.SetPath Then
                    ' Read the Flags and Constants fields.
                    .Add(ObjectExchangeHeaderType.Flags, New Byte() {streamReader.ReadByte()})
                    .Add(ObjectExchangeHeaderType.Constants, New Byte() {streamReader.ReadByte()})
                End If
            End With

            ' Parse the stream for headers.
            While objectExchangeData.Position <> packetSize
                Dim isRecognizedHeaderCode As Boolean = False
                Dim headerType As Byte = streamReader.ReadByte()
                If System.Enum.IsDefined(GetType(ObjectExchangeHeaderType), headerType) Then
                    isRecognizedHeaderCode = True
                End If

                ' Decide how to handle the header depending on the 2 high order
                ' bits of the header type.

                Dim higherOrderNibble As Byte = Convert.ToByte((headerType And 240))
                Select Case higherOrderNibble
                    Case 0, 64
                        ' Null terminated Unicode text, length prefixed
                        ' with 2 byte unsigned integer.
                        ' Byte sequence, length prefixed with 2 byte 
                        ' unsigned integer.
                        Dim headerDataLength As UShort = Convert.ToUInt16(ObjectExchangeHelper.FormatUInt16(streamReader.ReadUInt16()) - 3)
                        If isRecognizedHeaderCode Then
                            Dim headerData As Byte() = New Byte(headerDataLength - 1) {}
                            headerData = streamReader.ReadBytes(headerDataLength)
                            headers.Add(CType(headerType, ObjectExchangeHeaderType), headerData)
                        Else
                            objectExchangeData.Position += headerDataLength
                            ' This header is not supported by this sdk.
                            ' Ignore the header.
                        End If
                        Exit Select
                    Case 192

                        ' 4 byte quantity – transmitted in network byte order
                        ' (high byte first).
                        If isRecognizedHeaderCode Then
                            headers.Add(CType(headerType, ObjectExchangeHeaderType), streamReader.ReadBytes(4))
                        Else
                            objectExchangeData.Position += 4
                            ' This header is not supported by this sdk. 
                            ' Ignore the header.
                        End If
                        Exit Select
                    Case 128

                        ' 1 byte quantity. This header is not supported by
                        ' this sdk. Ignore the header.
                        objectExchangeData.Position += 1
                        Exit Select
                    Case Else

                        ' Since the 2 high order bits of the header type are 
                        ' not recognized, there are 3 possibilities.
                        ' The header is:
                        ' 1. An ObjectExchange packet field.
                        ' 2. A header that is not supported by this sdk.
                        ' 3. An invalid header.

                        If objectExchangeData.Position = 4 Then
                            ' 1. If we are still in the beginning of the 
                            '    packet, this is a Connect response.
                            ' Read the Obex version number, Flags and 
                            ' maximum obex packet length fields.
                            With headers
                                .Add(ObjectExchangeHeaderType.ObexVersionNumber, New Byte() {headerType})
                                .Add(ObjectExchangeHeaderType.Flags, streamReader.ReadBytes(1))
                                .Add(ObjectExchangeHeaderType.MaxObexPacketLength, streamReader.ReadBytes(2))
                            End With

                        Else
                            ' 2. Header that is not supported by this sdk. 
                            ' 3. Invalid header.
                            ' In both cases, we do not know how to handle this
                            ' header type, so raise an exception.
                            Throw New ObjectExchangeException(ObjectExchangeErrorType.StreamError, ObjectExchangeResources.Error_UnrecognizedCode)
                        End If
                        Exit Select
                End Select
            End While

        End Sub

        ''' <summary>
        ''' Writes the specified header to a stream using the specified Binary
        ''' Writer.
        ''' </summary>
        ''' <param name="headerType">Type of Header to write to a stream using
        ''' the specified Binary Writer.</param>
        ''' <param name="streamWriter">Binary Writer that is used to write the
        ''' specified headerType to a stream.</param>
        ''' <returns>The number of bytes written to the stream.</returns>
        Private Function WriteHeaderToStream(ByVal headerType As ObjectExchangeHeaderType, ByVal streamWriter As IO.BinaryWriter) As UShort

            Dim headerValue() As Byte = headers(headerType)

            With streamWriter
                Select Case headerType
                    Case ObjectExchangeHeaderType.ObexVersionNumber, ObjectExchangeHeaderType.Flags, ObjectExchangeHeaderType.Constants, ObjectExchangeHeaderType.MaxObexPacketLength
                        ' In case of an ObjectExchange packet Field, write the
                        ' field value.
                        .Write(headerValue)
                        Return Convert.ToUInt16(headerValue.Length)
                    Case ObjectExchangeHeaderType.ConnectionID, ObjectExchangeHeaderType.Length, ObjectExchangeHeaderType.Count, ObjectExchangeHeaderType.Time4ByteVersion

                        ' In case of ObjectExchange packet header value that is a
                        ' 4 byte quantity to be transmitted in network byte order
                        ' (high byte first), write the header tag and then the 4
                        ' byte value.
                        .Write(Convert.ToByte(headerType))
                        .Write(headerValue)
                        Return 5
                    Case Else

                        ' In case of ObjectExchange packet header value that is
                        ' 1. Null terminated Unicode text, length prefixed with 2
                        '    byte unsigned integer.
                        ' 2. A byte sequence, length prefixed with 2 byte unsigned
                        '    integer.
                        ' Write the header tag, then the 2 byte length in network
                        ' byte order (high byte first) and lastly the header value.

                        .Write(Convert.ToByte(headerType))
                        .Write(ObjectExchangeHelper.FormatUInt16(Convert.ToUInt16(headerValue.Length + 3)))
                        .Write(headerValue)
                        Return Convert.ToUInt16(headerValue.Length + 3)
                End Select
            End With

        End Function

#End Region

    End Class

End Namespace
